/*
 * Copyright (c) 2008 VMware, Inc.
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.in;

import wtkx.Alert;
import wtkx.Application;
import wtkx.collections.Dictionary;
import wtkx.collections.HashMap;
import wtkx.in.DisplayHost;
import wtkx.util.Properties;
import wtkx.wtk.ApplicationContext;
import wtkx.wtk.MessageType;


import netscape.javascript.JSObject;

import java.awt.Graphics;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;



/**
 * Applet used to host applications in a web browser.
 *
 * @author gbrown
 */
public class Applet
    extends java.applet.Applet 
{

    public static final String CLASS_NAME_PARAMETER = "wtkx_application";

    public static final String SCRIPT_NAME_PARAMETER = "wtkx_script";



    public class AppletProperties 
        extends Object
        implements Dictionary<String, String> 
    {
        protected AppletProperties(){
            super();
        }

        public String get(Object key) {
            return Applet.this.getParameter( (String)key);
        }
        public String put(String key, String value) {
            throw new UnsupportedOperationException();
        }
        public String remove(Object key) {
            throw new UnsupportedOperationException();
        }
        public boolean containsKey(Object key) {
            return (null != Applet.this.getParameter( (String)key));
        }
        public boolean isEmpty() {
            return false;
        }
    }

    public class InitCallback
        implements Runnable 
    {
        protected InitCallback(){
            super();
        }

        public void run() {

            URL documentBase = Applet.this.getDocumentBase();
            if (documentBase != null) {
                String queryString = documentBase.getQuery();
                if (queryString != null) {
                    String[] arguments = queryString.split("&");

                    for (int i = 0, n = arguments.length; i < n; i++) {
                        String argument = arguments[i];
                        String[] property = argument.split("=");

                        if (property.length == 2) {
                            String key, value;
                            try {
                                final String encoding = "UTF-8";
                                key = URLDecoder.decode(property[0], encoding);
                                value = URLDecoder.decode(property[1], encoding);
                                Applet.this.properties.put(key, value);
                            }
                            catch(UnsupportedEncodingException exception) {
                            }
                        }
                    }
                }
            }


            try {
                URL origin = null;
                URL codeBase = Applet.this.getCodeBase();
                if (codeBase != null) {
                    try {
                        origin = new URL(codeBase.getProtocol(), codeBase.getHost(),
                                         codeBase.getPort(), "");
                    }
                    catch (Exception exc) {
                        exc.printStackTrace();
                    }
                }
                String applicationClassName = Applet.this.getParameter(CLASS_NAME_PARAMETER);
                if (applicationClassName == null) {
                    Applet.this.application = new AppletScript(Applet.this);
                } 
                else {
                    Class applicationClass = Class.forName(applicationClassName);
                    Applet.this.application = (Application)applicationClass.newInstance();
                }
                
                Applet.this.applicationContext = 
                    new Browser(Applet.this, origin,
                                Applet.this.properties, 
                                Applet.this.application);

                DisplayHost displayHost = Applet.this.applicationContext.getDisplayHost();

                Applet.this.setLayout(new java.awt.BorderLayout());
                Applet.this.setFocusTraversalKeysEnabled(false);
                Applet.this.setBackground(null);
                Applet.this.add(displayHost);
            }
            catch(Exception exception) {
                Alert.alert(MessageType.ERROR, exception.getMessage(),
                            applicationContext.getDisplay());
                exception.printStackTrace();
            }
        }
    }

    public class StartCallback 
        implements Runnable 
    {
        protected StartCallback(){
            super();
        }

        public void run() {
            // Set focus to the display host
            DisplayHost displayHost = Applet.this.applicationContext.getDisplayHost();
            displayHost.requestFocus();

            if (application != null) {
                try {
                    application.startup(Applet.this.applicationContext.getDisplay(), Applet.this.properties);
                }
                catch(Exception exception) {
                    Alert.alert(MessageType.ERROR, exception.getMessage(),
                                Applet.this.applicationContext.getDisplay());
                    exception.printStackTrace();
                }
            }
        }
    }

    public class StopCallback
        implements Runnable 
    {
        protected StopCallback(){
            super();
        }

        public void run() {
            try {
                Applet.this.application.shutdown(false);
            }
            catch(Exception exception) {
                Alert.alert(MessageType.ERROR, exception.getMessage(),
                            Applet.this.applicationContext.getDisplay());
                exception.printStackTrace();
            }
        }
    }

    public class DestroyCallback 
        implements Runnable 
    {
        protected DestroyCallback(){
            super();
        }
            
        public void run() {
            // No-op
        }
    }

    private static Applet hostApplet = null;

    /**
     * Evaluates the specified script in the browser's JavaScript page
     * context and returns the result.
     * <p>
     * NOTE This feature requires that the applet run in its own JVM; see JDK
     * documentation on the <tt>separate_jvm</tt> applet parameter.
     */
    public static Object eval(String script) {
        if (hostApplet == null)
            throw new IllegalStateException("Application is not running in a web browser.");

        else {
            try {
                JSObject window = JSObject.getWindow(hostApplet);
                return window.eval(script);
            } 
            catch (Throwable throwable) {
                throw new UnsupportedOperationException(throwable);
            }
        }
    }


    protected Browser applicationContext;

    protected Properties properties;

    protected Application application;


    public Applet() {
        super();
        this.properties = new Properties(new AppletProperties());
    }

    public Application getApplication() {
        return application;
    }

    @Override
    public void init() {
        InitCallback initCallback = new InitCallback();

        if (java.awt.EventQueue.isDispatchThread())
            initCallback.run();
        else 
            ApplicationContext.queueCallback(initCallback, true);
    }
    @Override
    public void start() {
        StartCallback startCallback = new StartCallback();

        if (java.awt.EventQueue.isDispatchThread())
            startCallback.run();
        else
            ApplicationContext.queueCallback(startCallback, true);
    }
    @Override
    public void stop() {
        StopCallback stopCallback = new StopCallback();

        if (java.awt.EventQueue.isDispatchThread())
            stopCallback.run();
        else
            ApplicationContext.queueCallback(stopCallback, true);
    }
    @Override
    public void destroy() {
        DestroyCallback destroyCallback = new DestroyCallback();

        if (java.awt.EventQueue.isDispatchThread())
            destroyCallback.run();
        else 
            ApplicationContext.queueCallback(destroyCallback, true);
    }
    @Override
    public void update(Graphics graphics) {
        this.paint(graphics);
    }
}
